package org.tigr.htc.request;

/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.tigr.antware.shared.command.events.CmdEventDispatcher;
import org.tigr.antware.shared.command.events.CommandLifetimeLI;
import org.tigr.antware.shared.command.events.CommandRuntimeStatusLI;
import org.tigr.antware.shared.command.events.CommandStatusLI;
import org.tigr.antware.shared.command.events.FailureEvent;
import org.tigr.antware.shared.command.events.FinishEvent;
import org.tigr.antware.shared.command.events.InterruptEvent;
import org.tigr.antware.shared.command.events.ResumeEvent;
import org.tigr.antware.shared.command.events.StartEvent;
import org.tigr.antware.shared.command.events.SubmitEvent;
import org.tigr.antware.shared.command.events.SuspendEvent;
import org.tigr.htc.cmd.CmdState;
import org.tigr.htc.cmd.Command;
import org.tigr.htc.cmd.CommandStatus;
import org.tigr.htc.cmd.Param;
import org.tigr.htc.cmd.ParamType;
import org.tigr.htc.cmd.TaskGroup;

/**
 * The <b>HTCRequest</b> class implements the IRequest interface and handles 
 * creating a valid Command and a IProxyServer.  The HTCRequest delegates most of
 * its work to the Command class and a IProxyServer class.  HTCRequest does
 * make sure that the command object is writable only before submission and
 * that the command object is kept synchronized with the one on the server.
 * The Command class is a value object that represents a command to run on the
 * grid. The IProxyServer server class handles communication of the command
 * to the server and status updates.   HTCRequest also handles the environment
 * variables because these are not part of the Command object.
 * 
 * Refer to the IRequest documentation 
 */
public class HTCRequest implements IRequest, CommandStatusLI, CommandRuntimeStatusLI {
    static Logger log = Logger.getLogger(HTCRequest.class);
    final static String errorMessage = "Illegal set of attribute after submit";
    
    /**
     * <code> cmd</code> value object of all the command data
     *  
     */
    protected Command cmd;
    
    /**
     * <code>disp</code> handles event listener queue.
     *  
     */
    protected CmdEventDispatcher eventDisp;
    
    /**
     * <code>proxy</code> is the IPRoxyServer
     *  
     */
    protected IProxyServer proxy;
    
    /**
     * <code>env</code> is the environment for the request
     *  
     */
    protected HashMap<String, String> env;
    
    /**
     * <code>isSubmitted</code> tracks if the request has been submitted. Once a
     * request is submitted it becomes read-only.
     */
    private boolean isSubmitted;
    
    private boolean isCompleted;
    
    private String queue = null;
    
    private String gridId = null;
    
    /**
     * Create a new <code>HTCRequest</code> instance. The properties file contains
     * configuration information for HTCRequest like logging and configuration info for
     * the Proxy Server.
     *  
     */
    public HTCRequest() throws Exception {
        
        cmd = new Command();
        eventDisp = new CmdEventDispatcher();
        proxy = ProxyServerFactory.newProxyServer();
        
        // command must be type htc for a htcrequest
        cmd.setType("htc");
        
        // this request has not been submitted
        isSubmitted = false;
        isCompleted = false;
        
        // set user name to current user of jvm process
        cmd.setUserName(System.getProperty("user.name"));
        cmd.addStatusListener(this);
        cmd.addRuntimeStatusListener(this);
    }
    
    /**
     * Create a new <code>HTCRequest</code> instance. The properties file contains
     * configuration information for HTCRequest like logging and configuration info for
     * the Proxy Server. This constructor is used for reconnecting to the server to 
     * check on an existing request. This is typically necessary if the client died, or
     * was interrupted.
     *  
     */
    public HTCRequest(long id) throws Exception {
        eventDisp = new CmdEventDispatcher();
        proxy = ProxyServerFactory.newProxyServer();
        
        // this request has been submitted
        isSubmitted = true;
        
        // get command from server since we are reconnecting
        cmd = proxy.getCommand(id, this);
        
        // Add the status listeners to this command to listen to notifications
        cmd.addStatusListener(this);
        cmd.addRuntimeStatusListener(this);
    }
    
    
    /**
     * The constructor <code>HTCRequest</code> creates a new instance of the request
     * by cloning information from the old request specified as the parameter.
     * 
     * @param request
     * @throws Exception
     */
    public HTCRequest(IRequest request) throws Exception {
        
        log.debug(" create a new request from given request ");
        // 0. listeners don't get copied
        eventDisp = new CmdEventDispatcher();
        proxy = ProxyServerFactory.newProxyServer();
        
        // 1. copy command
        cmd = (Command) request.getCommandObj().clone();
        
        // 2. clear out id and status
        cmd.setID(0);
        cmd.setStatus(new CommandStatus());
        log.debug("Reset state to INIT ");
        cmd.setState(CmdState.INIT);
        cmd.setEndTime(0);
        cmd.setStartTime(0);
        cmd.setSubmitTime(0);

        log.debug("Setting Request queue to " + queue) ;
        
        // command must be of type htc for an htcrequest
        cmd.setType("htc");
        
        // this request has not been submitted
        isSubmitted = false;
        isCompleted = false;
        
        // set user name to current user of jvm process
        cmd.setUserName(System.getProperty("user.name"));
        cmd.addStatusListener(this);
        cmd.addRuntimeStatusListener(this);
    }
    
    //
    // Proxy server delegated methods
    // 
    
    /**
     * <code>submit</code> submit the request to the grid. If the request is valid and
     * is accepted by the cluster a unique positive id will be returned. If there is
     * error or the request is rejected the id returned will be less then 1.
     * 
     * @return a <code>long</code> a unique id assigned to the request
     * @exception IllegalAccessException
     *                if a request has already been submitted
     * @exception IOException
     *                if a network problem occurs
     */
    public long submit() throws IllegalAccessException, IOException {
        log.debug("Submitting " + cmd.getID() + " to the Grid");
        
        if (! isSubmitted) {
            long id = 0;
            if (cmd.isValid()) {
                id = proxy.submit(this);
                if (id > 0) {
                    isSubmitted = true;
                }
            } else {
                throw new IllegalAccessException("Request is invalid or is missing data");
            }
            return id;
        } else {
            throw new IllegalAccessException("Request already submitted");
        }
    }
    
    /**
     * <code>waitForRequest</code> wait for the request to finish.
     * 
     * @return a <code>long</code> id for the request that finished
     * @exception IllegalAccessException
     *                if request hasn't been submitted
     * @exception InterruptedException
     *                if interrupted while waiting
     */
    public synchronized long waitForRequest()
    throws InterruptedException, IllegalAccessException {
        if (isSubmitted) {
            log.info("HTCRequest.waitForRequest " + cmd.getID());
            // register with the proxy server
            while (!isCompleted) {
                log.debug(" thread going into wait");
                this.wait();
                log.info(
                        "request " + cmd.getID() + " woken up, state is " + cmd.getState());
                log.info("isCompleted is " + isCompleted);
            }
            log.debug("return request id");
            return cmd.getID();
        } else {
            throw new IllegalAccessException(
                    "Could not wait for a request(" + cmd.getID() 
                    + ") that is not submitted");
        }
    }
    
    /**
     * <code>waitForRequest</code> wait for the request to finish.
     * 
     * @return a <code>long</code> id for the request that finished
     * @exception IllegalAccessException
     *                if request hasn't been submitted
     * @exception InterruptedException
     *                if interrupted while waiting
     */
    //    public synchronized long waitForRequest(long p_millis)
    //	throws InterruptedException, IllegalAccessException {
    // time not implemented yet
    //	return this.waitForRequest();
    //    }
    
    /**
     * <code>stop</code> send a stop to the request. This method doesn't wait for the
     * command to stop before it returns. Delegated to proxy.
     * 
     * @exception IllegalAccessException
     *                if the request hasn't been submitted
     */
    public void stop() throws IllegalAccessException, IOException {
        if (isSubmitted) {
            proxy.stop(this);
        } else {
            throw new IllegalAccessException("Request has not been submitted yet.");
        }
    }
    
    /* (non-Javadoc)
     * @see org.tigr.htc.request.IRequest#getMonitorHost()
     */
    public String getMonitorHost() throws IOException {
        return proxy.getMonitorHost(this);
    }

    public TaskGroup getTasks () throws IOException, IllegalStateException {
        return proxy.getTasks(this);
    }
    
    /**
     * <code>resume</code> send a resume command to a request that has been
     * held/suspended by the user. This will cause the request to be put back in a queue
     * to be scheduled. This method doesn't wait for the command to stop before it
     * returns. Delegated to proxy.
     * 
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void resume() throws IllegalAccessException, IOException {
        if (isSubmitted) {
            proxy.resume(this);
        } else {
            throw new IllegalAccessException("Request has not been submitted yet.");
        }
    }
    
    /**
     * <code>hold</code> send a hold/suspend command to a request. Delegated to proxy.
     * 
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void hold() throws IllegalAccessException, IOException {
        if (isSubmitted) {
            proxy.hold(this);
        } else {
            throw new IllegalAccessException("Request has not been submitted yet.");
        }
    }
    
    /**
     * <code>disconnect</code> client no longer wants event updates from server.
     * delegate to proxy server.
     * 
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void disconnect() throws IllegalAccessException, IOException {
        if (isSubmitted) {
            proxy.disconnect(this);
        } else {
            return;
        }
    }
    
    public Command getCommandObj() {
        return cmd;
    }
    
    public Map status() {
        if (isSubmitted) {
            return proxy.getStatus(this);
        } else {
            return proxy.getStatus(this);
        }
    }
    
    //
    // Command delegated methods
    //
    
    //
    // set username, be very, very quiet
    //
    protected void setUserName(String userName)
    throws IllegalAccessException, IllegalArgumentException {
        
        if (!isSubmitted) {
            if (userName.length() > 0) {
                cmd.setUserName(userName);
            } else {
                throw new IllegalArgumentException("Invalid User Name " + userName);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getUserName</code> gets the username that the request will be run under.
     * The username defaults to the user that owns the jvm process which is defined by
     * the user system property.
     * 
     * @return a <code>String</code> value
     */
    public String getUserName() {
        return cmd.getUserName();
    }
    
    public long getID() {
        return cmd.getID();
    }
    
    public String getName() {
        return cmd.getName();
    }
    
    /**
     * <code>setName</code> defined name for the command. It can be any non-empty
     * string. Here for user to have an English name instead of just a command id.
     * 
     * Example: setName("My Blast Command");
     * 
     * @param name
     *            a <code>String</code> that contains the command name
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setName(String name)
    throws IllegalAccessException, IllegalArgumentException {
        if (!isSubmitted) {
            if (name.length() > 0) {
                cmd.setName(name);
            } else {
                throw new IllegalArgumentException("Invalid Name " + name);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getCommand</code> get the full path of command.
     * 
     * @return a <code>String</code> value
     */
    public String getCommand() {
        return cmd.getCommand();
    }
    
    /**
     * <code>setCommand</code> set the FULL path to the command/executable you want to
     * run. This is required to have a valid request and cannot be empty or null.
     * 
     * Example: setCommand("/usr/local/bin/blastp");
     * 
     * @param command
     *            a <code>String</code> that contains full command to run
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setCommand(String command) throws IllegalAccessException, IllegalArgumentException {
        if (! isSubmitted) {
            if ((command != null) && (command.length() > 0)) {
                cmd.setCommand(command);
            } else {
                throw new IllegalArgumentException("Invalid command: " + command);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>setGroup</code> set the group that is running the command. Group must be
     * set to have a valid request.
     * 
     * @param group
     *            a <code>String</code> that contains the group name
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setGroup(String group) throws IllegalAccessException,
			IllegalArgumentException {
        if (!isSubmitted) {
            if ((group != null) && (group.length() > 0)) {
                cmd.setGroup(group);
            } else {
                throw new IllegalArgumentException("Invalid Group " + group);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getGroup</code> get the group for the request.
     * 
     * @return a <code>String</code> value
     */
    public String getGroup() {
        return cmd.getGroup();
    }
    
    /**
     * <code>getState</code> gets the current state of the request. The valid state are
     * defined in the enum CmdState.
     * 
     * @return a <code>CmdState</code> value
     */
    public CmdState getState() {
        return cmd.getState();
    }
    
    /**
     * <code>getState</code> gets the current state of the request. The valid state are
     * defined in the enum CmdState.
     * 
     * @return a <code>CmdState</code> value
     */
    public int getReturnValue() {
        return cmd.getStatus().getReturnValue();
    }
    
    
    /**
     * <code>getEmail</code> get email address associated with request if it needs to
     * be different than the username.
     * 
     * @return a <code>String</code> value
     */
    public String getEmail() {
        return cmd.getEmail();
    }
    
    /**
     * <code>setEmail</code> set the email address associated with the request,
     * 
     * @param email
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setEmail(String email) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if ((email != null) && (email.length() > 0)) {
                cmd.setEmail(email);
            } else {
                throw new IllegalArgumentException("Invalid email " + email);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getNotifyScript</code> get script to notify when command is finished.
     * 
     * @return a <code>String</code> value
     */
    public String getNotifyScript() {
        return cmd.getNotifyScript();
    }
    
    /**
     * <code>setNotifyScript</code> set script to call when command is finished. The
     * script is executed and should expect the request id, email address, and return
     * value passed to it.
     * 
     * If you were to register a "/path/to/script" then it will be called with following
     * command line arguments:
     * /path/to/script --requestID=id --returnValue=val --message="string"
     * --email=<email>
     * 
     * @param notifyScript
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setNotifyScript(String notifyScript)
    throws IllegalAccessException, IllegalArgumentException {
        if (! isSubmitted) {
            if ((notifyScript != null) && (notifyScript.length() > 0)) {
                cmd.setNotifyScript(notifyScript);
            } else {
                throw new IllegalArgumentException("Invalid notification script: " + notifyScript);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getOutput</code> get the file that STDOUT is being redirected to.
     * 
     * @return a <code>String</code> value
     */
    public String getOutput() {
        return cmd.getOutput();
    }
    
    /**
     * <code>setOutput</code> setter for a file to send the what the command would
     * usually send to the screen and STDOUT. This is needed since the command will be
     * running remotely. This can be an absolute path or relative to InitialDir. If not
     * set all output ignored and lost.
     * 
     * @param output
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setOutput(String output) throws IllegalArgumentException,
			IllegalAccessException {
        if (! isSubmitted) {
            if ((output != null) && (output.length() > 0)){
            	
            	// Set the STDOUT for the Command
            	File outputFile = new File(output);
            	cmd.setOutput(output);
            	
            	// If this file exists and not writable, throw exception.
            	if(outputFile.exists()) {
            		if (outputFile.canWrite()) {
            			// File is writable, do Nothing
            		}
            		else {
            			// File is not writable, throw exception
                		throw new IllegalArgumentException("No write permissions to the STDOUT file " + output);
                	}
            	}
            } else {
                throw new IllegalArgumentException("Invalid output file: " + output);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getInput</code> get the file that STDIN is being read from.
     * 
     * @return a <code>String</code> value
     */
    public String getInput() {
        return cmd.getInput();
    }
    
    /**
     * <code>setInput</code> setter for a file to read standard input from. This is
     * needed since the command will be run remotely and and input can't be read from the
     * keyboard. If not set input is ignored. This can be an absolute path or relative to
     * InitialDir.
     * 
     * @param input
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setInput(String input) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if ((input != null) && (input.length() > 0)) {
                cmd.setInput(input);
            } else {
                throw new IllegalArgumentException("Invalid input file: " + input);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getError</code> get the file that STDERR is being redirected to.
     * 
     * @return a <code>String</code> value
     */
    public String getError() {
        return cmd.getError();
    }
    
    /**
     * <code>setError</code> setter for a file to send the what the command would
     * usually send to the screen or STDERR. This is needed since the command will be
     * running remotely. This can be an absolute path or relative to InitialDir. If not
     * set all output ignored and lost.
     * 
     * @param error
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setError(String error) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if ((error != null) && (error.length() > 0)) {
            	File errorFile = new File(error);
            	cmd.setError(error);
            	
            	// If this file exists, make sure it is writable 
            	if (errorFile.exists()) {
            		if (errorFile.canWrite()) {
            			// File is writable. Do nothing
            		}
            		else {
            			// File is not writable, throw exception
                		throw new IllegalArgumentException("No write permissions to stderr file " + error);
                	}
            	}
            } else {
                throw new IllegalArgumentException("Invalid Error Ouput " + error);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getInitialDir</code> get the path of the initial working directory for the
     * request.
     * 
     * @return a <code>String</code> value
     */
    public String getInitialDir() {
        return cmd.getInitialDir();
    }
    
    /**
     * <code>setInitialDir</code> set the current working path of the command when it
     * is executed. If this initial directory is set everything else (such as output &
     * error) can be relative from it EXCEPT for the command.
     * 
     * @param dir a <code>String</code> value
     * @exception IllegalAccessException
     *                if set is attempted after submission
     */
    public void setInitialDir(String dir) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if (dir.length() > 0) {
                cmd.setInitialDir(dir);
            } else {
                throw new IllegalArgumentException("Invalid initial directory: " + dir);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>addParam</code> adds a value to the end of the command line when command
     * is invoked. The command line arguments are appended in the order that they are
     * added.
     * 
     * Example: setCommand("/foo/test.sh"); add_param("-v"); add_param("database user");
     * 
     * final command -> /foo/test.sh -v database user
     * 
     * @param arg a <code>String</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addParam(String arg) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if (arg.length() > 0) {
                cmd.addParam(new Param(arg));
            } else {
                throw new IllegalArgumentException("Invalid param: " + arg);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>addParam</code> adds a key=value to the end of the command line when
     * command is invoked. The command line arguments are appended in the order that they
     * are added.
     * 
     * Example: setCommand("/foo/test.sh"); add_param("-v"); add_param("database",
     * "user");
     * 
     * final command -> /foo/test.sh -v database=user
     * 
     * @param key
     *            a <code>String</code> value
     * @param value
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addParam(String key, String value)
			throws IllegalAccessException, IllegalArgumentException {
        if (! isSubmitted) {
            cmd.addParam(key, value);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>addParam</code> adds a parameter to the end of the command line when
     * command is invoked. The command line arguments are appended in the order that they
     * are added. Depending on the paramter type how the arguments are added are
     * different.
     * 
     * List of Types
     * 
     * FILE: If the type is FILE then the value should be the file that contains a list
     * of values to use. The request will be run once with each line in the file as the
     * value.
     * 
     * Example: setCommand("/foo/test.sh"); add_param("-v"); add_param("-d", "/foo/data",
     * ParamType.FILE);
     * 
     * /foo/data contains: line 1. /usr/local/db/noest line 2. /usr/tmp/db
     * 
     * final commands: /foo/test.sh -v -d=/usr/local/db/noest /foo/test.sh -v
     * -d=/usr/tmp/db
     * 
     * TEMPFILE: The same as FILE except that the file containing the values is deleted
     * once the request is finished.
     * 
     * DIR: If the type is DIR then the value is a directory. The request will be run
     * once with each file in the directory as the value.
     * 
     * @param key
     *            a <code>String</code> value
     * @param value
     *            a <code>String</code> value
     * @param type
     *            a <code>ParamType</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addParam(String key, String value, ParamType type)
    throws IllegalAccessException, IllegalArgumentException {
        if (!isSubmitted) {
            if (type != ParamType.PARAM) {
                log.debug("Setting the command type to mw");
                cmd.setType("mw");
            }
            cmd.addParam(key, value, type);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>addParam</code> The same as addParam(key, value, type) except there isn't
     * a key added to each value.
     * 
     * @param value
     *            a <code>String</code> value
     * @param type
     *            a <code>ParamType</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addParam(String value, ParamType type)
			throws IllegalAccessException, IllegalArgumentException {
        if (! isSubmitted) {
            if (value.length() > 0) {
                if (type != ParamType.PARAM) {
                    cmd.setType("mw");
                }
                cmd.addParam(value, type);
            } else {
                throw new IllegalArgumentException("Invalid param: " + value);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>addParams</code> adds a map of params one at a time to the end of the
     * command line. It iterates over the hash and does addParam(key, value). The order
     * is up to Map.
     * 
     * Example: command key1=value1 key2=value2 key3=value3
     * 
     * @param args
     *            a <code>Map</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addParams(Map args) throws IllegalAccessException,
			IllegalArgumentException {
		if (! isSubmitted) {
            for (Iterator i = args.entrySet().iterator(); i.hasNext();) {
                Entry e = (Entry) i.next();
                cmd.addParam((String) e.getKey(), (String) e.getValue());
            }
        }
    }
    
    /**
     * <code>addAnonParams</code>
     * 
     * @param args a <code>Map</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void addAnonParams(Map args) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            for (Iterator i = args.entrySet().iterator(); i.hasNext();) {
                Entry e = (Entry) i.next();
                cmd.addParam(new Param((String) e.getValue()));
            }
        }
    }
    
    /**
     * <code>getOpSys</code> get target operating system
     * 
     * @return a <code>String</code> value
     */
    public String getOpSys() {
        return cmd.getConfig().getOpSys();
    }
    
    /**
     * <code>setOpSys</code> set the target operating system for the command. Currently
     * supported are "Linux", "Solaris", "OSF1".
     * 
     * @param p_opsys
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setOpSys(String p_opsys) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setOpSys(p_opsys);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getHosts</code> get
     * 
     * @return a <code>String</code> value
     */
    public String getHosts() {
        return cmd.getConfig().getHosts();
    }
    
    /**
     * <code>getLogLocation</code> get
     * 
     * @return a <code>String</code> value
     */
    public String getLogLocation() {
        return cmd.getConfig().getLogLocation();
    }
    
    /**
     * <code>setHosts</code> set the target operating system for the command. Currently
     * supported are "Linux", "Solaris", "OSF1".
     * 
     * @param p_opsys
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setHosts(String p_hosts) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setHosts(p_hosts);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    public String getPassThrough() {
        return cmd.getConfig().getPassThrough();
    }
    
    public void setPassThrough(String passThrough)
			throws IllegalAccessException, IllegalArgumentException {
		if (! isSubmitted) {
			cmd.getConfig().setPassThrough(passThrough);
		} else {
			throw new IllegalAccessException(HTCRequest.errorMessage);
		}
    }
    
    /**
     * <code>setLength</code> of a individual command. Acceptable values of "short",
     * "medium", "long", "forever"
     * 
     * short job is less than 4 hours medium job is less than 12 hours long is less than
     * 24 hours forever don't ever stop
     * 
     * @param length
     *            a <code>String</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setLength(String length) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setLength(length);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getLength</code> get value of length
     * 
     * @return a <code>String</code> value
     */
    public String getLength() {
        return cmd.getConfig().getLength();
    }
    
    /**
     * <code>setMemory</code> set the minimum amount of memory a job requires in MB.
     * 
     * @param p_memory
     *            an <code>int</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setMemory(int p_memory) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setMemory(p_memory);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>getMemory</code> get the minimum amount of memory a job requires in MB.
     * 
     * @return an <code>int</code> value
     */
    public int getMemory() {
        return cmd.getConfig().getMemory();
    }
    
    public int getPriority() {
        return cmd.getConfig().getPriority();
    }
    
    public void setPriority(int p_priority) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setPriority(p_priority);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    public void setCmdClass(String cmdClass)
        throws IllegalAccessException, IllegalArgumentException {
    	log.debug("Setting command class to " + cmdClass);
        if (! isSubmitted) {
            cmd.getConfig().setCmdClass(cmdClass);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    public String getCmdClass() {
        return cmd.getConfig().getCmdClass();
    }
    
    /**
     * <code>setGetEnv</code> set the flag that determines if the environment is sent
     * with the request or not. The environment is either the environment of the JVM or set by
     * user by passing a vector to the setEnvList method.
     * 
     * @param env
     *            a <code>boolean</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setGetEnv(boolean env) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            cmd.getConfig().setGetenv(env);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    /**
     * <code>isGetEnv</code> will the environment be sent with the request.
     * 
     * @return a <code>boolean</code> value
     */
    public boolean isGetEnv() {
        return cmd.getConfig().isGetenv();
    }
    
    /**
     * <code>setEnvList</code> set the environment variables for the request. The
     * vector should contain strings of the format key=value.
     * 
     * @param env
     *            a <code>Map</code> value
     */
    public void setEnv(Map env) {
        this.env = new HashMap<String, String>(env);
        cmd.getConfig().setGetenv(true);
    }
    
    /**
     * <code>getEnvList</code> get the environment vector for the request.
     * 
     * @return a <code>Vector</code> value
     */
    public Map<String, String> getEnv() {
        return this.env;
    }
    
    /**
     * <code>getRetryCount</code> get the number of times to retry a command before
     * giving up and setting the state to Failure
     * 
     * @return an <code>int</code> value
     */
    public int getRetryCount() {
        return cmd.getRetryCount();
    }
    
    /**
     * <code>setRetryCount</code> set the number of times to retry a command before
     * giving up and setting the state to Failure.
     * 
     * @param retryCount an <code>int</code> value
     * @exception IllegalAccessException
     *                if an error occurs
     */
    public void setRetryCount(int retryCount) {
        cmd.setRetryCount(retryCount);
    }
    
    /**
     * <code>getTimes</code> get the number of time to run a command if the number of
     * times was set.
     * 
     * @return an <code>int</code> value
     */
    public int getTimes() {
        return cmd.getTimes();
    }
    
    public void setTimes(int times) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if (times > 0) {
                cmd.setTimes(times);
            } else {
                throw new IllegalArgumentException(
                        "Invalid number of time to run the command: " + times);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    
    public int getMaxWorkers() {
        return cmd.getMaxWorkers();
    }
    
    public void setMaxWorkers(int max) throws IllegalAccessException,
			IllegalArgumentException {
        if (! isSubmitted) {
            if (max > 0) {
                cmd.setMaxWorkers(max);
            } else {
                throw new IllegalArgumentException(
                        "Invalid number for maximum workers " + max);
            }
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    public void setRunningTime(long minutes) throws IllegalAccessException{
        if (!isSubmitted) {
            cmd.getConfig().setRunningTime(minutes);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }
    
    public long getRunningTime() {
    	return cmd.getConfig().getRunningTime();
    }
    
    public void setEvictable(boolean evictable) throws IllegalAccessException{
        if (! isSubmitted) {
            cmd.getConfig().setEvictable(evictable);
        } else {
            throw new IllegalAccessException(HTCRequest.errorMessage);
        }
    }

    public boolean isEvictable() {
    	return cmd.getConfig().isEvictable();
    }
    
    public String getGridJobId() throws IOException {
    	// Get the grid id from the corresponding Command 
    	log.debug("In getGridJobId for " + this.getID());
    	gridId = cmd.getGridJobID();
    	
    	// If the grid id is null, try to get it from the server.
    	// For local submissions, this code would not be executed.
    	if (gridId == null) {
    		Command cmd = proxy.getCommand(getID(), this);
    		gridId = cmd.getGridJobID();
    	}
    	
    	log.debug("Grid id for " + this.getID() + " is " + gridId);
    	return gridId;
    }
    
    public void setGridJobId(String gridId) {
    	this.gridId = gridId;
    }
    
    /*
     * Event Handling
     */
    
    /**
     * <code>addRuntimeStatusListener</code> add p_listen to queue of listeners that
     * what to receive RuntimeStatus events
     * 
     * @param listener
     *            a <code>CommandRuntimeStatusLI</code> value
     */
    public void addRuntimeStatusListener(CommandRuntimeStatusLI listener) {
        eventDisp.addRuntimeStatusListener(listener);
    }
    
    /**
     * <code>removeRuntimeStatusListener</code> remove p_listen from the queue of
     * listeners that what to receive RuntimeStatus events
     * 
     * @param listener
     *            a <code>CommandRuntimeStatusLI</code> value
     */
    public void removeRuntimeStatusListener(CommandRuntimeStatusLI listener) {
        eventDisp.removeRuntimeStatusListener(listener);
    }
    
    /**
     * <code>addLifetimeListener</code> add p_listen to queue of listeners that what to
     * receive lifetime events
     * 
     * @param listener
     *            a <code>CommandLifetimeLI</code> value
     */
    public void addLifetimeListener(CommandLifetimeLI listener) {
        eventDisp.addLifetimeListener(listener);
    }
    
    /**
     * <code>removeLifetimeListener</code> remove p_listen from the queue of listeners
     * that what to receive lifetime events
     * 
     * @param listener
     *            a <code>CommandLifetimeLI</code> value
     */
    public void removeLifetimeListener(CommandLifetimeLI listener) {
        eventDisp.removeLifetimeListener(listener);
    }
    
    /**
     * <code>addStatusListener</code> add p_listen to queue of listeners that what to
     * receive Status events
     * 
     * @param listener
     *            a <code>CommandStatusLI</code> value
     */
    public void addStatusListener(CommandStatusLI listener) {
        eventDisp.addStatusListener(listener);
    }
    
    /**
     * <code>removeStatusListener</code> remove p_listen from the queue of listeners
     * that what to receive Status events
     * 
     * @param listener a <code>CommandStatusLI</code> value
     */
    public void removeStatusListener(CommandStatusLI listener) {
        eventDisp.removeStatusListener(listener);
    }
    
    // 
    // methods to handle events when they happen
    // all are delegated to CmdEventDispatcher
    //
    public void commandStarted(StartEvent event) {
        fireStartEvent(event);
    }
    
    public void commandFinished(FinishEvent event) {
        fireFinishEvent(event);
    }
    
    public void commandRestarted(StartEvent event) {
        fireRestartEvent(event);
    }
    
    public void commandInterrupted(InterruptEvent event) {
        fireInterruptEvent(event);
    }
    
    public void commandFailed(FailureEvent event) {
        fireFailureEvent(event);
    }
    
    public void commandResumed(ResumeEvent event) {
        fireResumeEvent(event);
    }
    
    public void commandSuspended(SuspendEvent event) {
        fireSuspendEvent(event);
    }
    
    public void commandSubmitted(SubmitEvent event) {
        fireSubmitEvent(event);
    }
    
    /**
     * <code>fireSubmitEvent</code> Notify all listeners that have registered interest
     * for notification on this event type.
     * 
     * @param event a <code>SubmitEvent</code> value
     */
    public void fireSubmitEvent(SubmitEvent event) {
        eventDisp.fireSubmitEvent(event);
    }
    
    /**
     * <code>fireStartEvent</code>. Notify all listeners that have registered interest
     * for notification on this event type .
     * 
     * @param event
     *            a <code>StartEvent</code> value
     */
    public void fireStartEvent(StartEvent event) {
        eventDisp.fireStartEvent(event);
    }
    
    /**
     * <code>fireFinishEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     * 
     * @param event
     *            a <code>FinishEvent</code> value
     */
    public synchronized void fireFinishEvent(FinishEvent event) {
        eventDisp.fireFinishEvent(event);
        log.info("Finish event, set isCompleted to true");
        isCompleted = true;
        log.debug("Notify all waiting");
        this.notifyAll();
    }
    
    /**
     * <code>fireInterruptEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     * 
     * @param event an <code>InterruptEvent</code> value
     */
    public synchronized void fireInterruptEvent(InterruptEvent event) {
        eventDisp.fireInterruptEvent(event);
        log.info("Interrupt event, set isCompleted to true");
        isCompleted = true;
        log.debug("Notify all waiting");
        this.notifyAll();
    }
    
    /**
     * <code>fireResumeEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     * 
     * @param event
     *            a <code>ResumeEvent</code> value
     */
    public void fireResumeEvent(ResumeEvent event) {
        eventDisp.fireResumeEvent(event);
    }
    
    /**
     * <code>fireRestartEvent</code>. Notify all listeners that have registered
     * interest for notification on this event type.
     * 
     * @param event a <code>StartEvent</code> value
     */
    public void fireRestartEvent(StartEvent event) {
        eventDisp.fireRestartEvent(event);
    }
    
    public synchronized void fireFailureEvent(FailureEvent event) {
        eventDisp.fireFailureEvent(event);
        log.info("Failure event, set isCompleted to true");
        isCompleted = true;
        log.debug("Notify all waiting");
        this.notifyAll();
    }
    
    public void fireSuspendEvent(SuspendEvent event) {
        eventDisp.fireSuspendEvent(event);
    }
    
    public String toXML() {
        return cmd.toString();
    }
    
    /**
     * <code>setSubmitTime</code> set the submission Time.
     *
     * @param submitTime
     *            a <code>Date</code> of submit time value
     */
    public void setSubmitTime(Date submitTime) {
        this.cmd.setSubmitTime(submitTime.getTime());
    }

    /**
     * <code>getSubmitTime</code> get the submission time.
     *
     * @return a <code>Date</code> of submit time
     */
    public Date getSubmitTime() {
        return new Date(this.cmd.getSubmitTime());
    }

    /**
     * <code>setStartTime</code> set Start Time.
     *
     * @param startTime a <code>Date</code> of start time value
     */
    public void setStartTime(Date startTime) {
        this.cmd.setStartTime(startTime.getTime());
    }

    /**
     * <code>getStartTime</code> get Start Time.
     *
     * @return a <code>Date</code> of start time
     */
    public Date getStartTime() {
        return new Date(this.cmd.getStartTime());
    }

    /**
     * <code>setEndTime</code> set End Time
     *
     * @param endTime a <code>Date</code> of end time
     */
    public void setEndTime(Date endTime) {
        this.cmd.setEndTime(endTime.getTime());
    }

    /**
     * <code>getEndTime</code> get End Time
     *
     * @return a <code>Date</code> value
     */
    public Date getEndTime() {
        return new Date(this.cmd.getEndTime());
    }
    
}
